package club.jdiy.dev.service.impl;

import club.jdiy.admin.dao.GuidDao;
import club.jdiy.admin.util.IdUtil;
import club.jdiy.core.AppContext;
import club.jdiy.core.base.JDiyBaseService;
import club.jdiy.core.ex.JDiyException;
import club.jdiy.core.ex.JDiyFormException;
import club.jdiy.core.ex.JDiySqlException;
import club.jdiy.core.sql.Args;
import club.jdiy.core.sql.Dao;
import club.jdiy.core.sql.Rs;
import club.jdiy.core.sql.TableInfo;
import club.jdiy.dev.dao.JDiyUiDao;
import club.jdiy.dev.entity.JDiyUi;
import club.jdiy.dev.helper.TreeHelper;
import club.jdiy.dev.meta.*;
import club.jdiy.dev.service.JDiyUiService;
import club.jdiy.dev.types.FormatTpl;
import club.jdiy.dev.types.InputTpl;
import club.jdiy.dev.types.ManyTpl;
import club.jdiy.dev.types.OptTpl;
import club.jdiy.dev.view.*;
import club.jdiy.utils.BeanUtils;
import club.jdiy.utils.Md5Utils;
import club.jdiy.utils.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Service
public class JDiyUiServiceImpl extends JDiyBaseService<JDiyUi, JDiyUiDao, String>
        implements JDiyUiService {

    @Override
    @Transactional
    public Rs do_save(String dataId, Map<String, String> qo, FormUiMeta uiMeta, FormHandler handler, FtlParser parser) throws Exception {
        Rs rs = context.getDao().rs(uiMeta.getMainTable(), dataId);
        parser.addVariable("vo", rs);
        //todo 这儿不能加putall, 因此注了，加了密码框未修改时会被空值覆盖掉  rs.putAll(qo);
        if (rs.isNew()) {
            //当有实际id传入，则以该id值保存
            //例如菜单直接绑定一个form(通常是直接修改某条记录，但若这条记录不存在．则系统应该是按该id值自动创建它，而不是产生新的id)
            if (StringUtils.hasText(dataId) && !"0".equals(dataId)) rs.set(rs.getPrimaryKey(), dataId);
            else
                switch (uiMeta.getPkTpl()) {
                    case char10:
                        rs.set(rs.getPrimaryKey(), IdUtil.newId());
                        break;
                    case times:
                        rs.set(rs.getPrimaryKey(), String.valueOf(IdUtil.getTimes()));
                        break;
                    case nows:
                        rs.set(rs.getPrimaryKey(), IdUtil.getNows());
                        break;
                    case dates:
                        String s = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
                        rs.set(rs.getPrimaryKey(), guidDao.getNo(s, uiMeta.getPkLen()));
                        break;
                    case pres:
                        String p = parser.parse(uiMeta.getPkPre());
                        rs.set(rs.getPrimaryKey(),
                                ("jdiy_user".equalsIgnoreCase(rs.getTable()) ? "" : p) + //这有一个BUG,jdiy_user不需要把p加到前面,判断一下
                                        guidDao.getNo(p, uiMeta.getPkLen()));
                        break;
                    case uuid:
                        rs.set(rs.getPrimaryKey(), UUID.randomUUID().toString().replaceAll("-", ""));
                        break;
                    default:
                        rs.set(rs.getPrimaryKey(), null);
                }
        }


        //隐藏域：
        if (uiMeta.getHiddenParams() != null && !"".equals(uiMeta.getHiddenParams())) {
            String[] sa = uiMeta.getHiddenParams().split("\\n");
            for (String ss : sa) {
                String[] sb = ss.split(":");
                if (sb.length == 2) {
                    String v = sb[1].trim(), f = sb[0].trim();
                    if (rs.isNew() || f.startsWith("@")) {
                        rs.set(f.startsWith("@") ? f.substring(1).trim() : f, parser.parse(v));
                    }
                }
            }
        }

        TreeUiMeta __treeUiMeta = null;//如果有值，需要更新树特有字段

        for (InputMeta input : uiMeta.getInputs()) {
            String cField = StringUtils.isEmpty(input.getField()) ? input.getId() : input.getField();
            String v = qo.get(cField);
            if (FormatTpl.pwdAgain == input.getFormat()) continue;

            switch (input.getType()) {
                case input:
                    if (FormatTpl.pwd == input.getFormat()) {
                        if (v == null || "".equals(v)) continue;
                        if ("md5".equals(input.getPwdType())) {
                            v = Md5Utils.md5(v);
                        } else if ("md5salt".equals(input.getPwdType())) {
                            v = Md5Utils.encrypt(v);
                        }
                    } else if (FormatTpl.joinEntity == input.getFormat()) {
                        if (StringUtils.hasText(qo.get(cField))) {
                            List<?> ida = em.createQuery("select o.id from " + input.getJoinEntity() + " o where o." + input.getJoinField() + "='" +
                                            qo.get(cField).replaceAll("'", "''") + "'")
                                    .setMaxResults(1)
                                    .getResultList();
                            if (ida == null || ida.size() < 1 || ida.get(0) == null)
                                throw new JDiyFormException("[" + input.getLabel() + "] 输入信息无效！", cField);
                            v = ida.get(0).toString();
                        } else {
                            v = null;
                        }
                        rs.set(cField, v);//外键返查v=null也要更新
                    } else if (FormatTpl.joinTable == input.getFormat()) {
                        if (StringUtils.hasText(qo.get(cField))) {
                            TableInfo joinTableInfo = context.getDao().getTableInfo(input.getJoinTable());
                            Rs oj = context.getDao().rs(new Args(joinTableInfo.getTableName(),
                                    input.getJoinField() + "='" + qo.get(cField) + "'", joinTableInfo.getPrimaryKey()));
                            if (oj.isNew())
                                throw new JDiyFormException("[" + input.getLabel() + "] 输入信息无效！", cField);
                            v = oj.id();
                        } else {
                            v = null;
                        }
                        rs.set(cField, v);//外键返查v=null也要更新
                    }
                    break;
                case switcher:
                    String[] va = input.getOnv().split("\\|");
                    v = va["on".equals(v) ? 0 : 1].trim();
                    break;
                case treeSelect:
                    TreeUiMeta tm = dao.getUiMeta(input.getTreeId());
                    if (tm.getPidField().equals(cField) && tm.getMainTable().equals(uiMeta.getMainTable()))
                        __treeUiMeta = tm;
                    break;
                /*case checkbox:
                //因v被置空导致handler.beforeSave里面无法取到多选框或loopup控件的值，所以注掉了，
                //之前置空原因是，这些控件不直接写本表，可以随便绑个字段，注掉后，就只能绑自定义字段（即表中不存在的字段）,如果随便绑一个字段，保存就会把那个字段更新掉
                //若后续无业务问题，这个直接去掉．
                    if (ManyTpl.OneToMany == input.getManyType() || ManyTpl.ManyToMany == input.getManyType()) {
                        v = null;//many类型更新的是外部表，下面单独处理（且需放在本表save之后）
                    }
                    break;
                case lookup:
                    if (ManyTpl.OneToMany == input.getLookupType() || ManyTpl.ManyToMany == input.getLookupType()) {
                        v = null;
                    }
                    break;*/
            }

            //验重：
            if (input.isExistsChk() && !StringUtils.isEmpty(v)) {
                String existsSql = input.getField() + "='" + v + "' and o." + rs.getPrimaryKey() + "<>'" + rs.id() + "'";
                if (StringUtils.hasText(input.getExistsFilter())) {
                    existsSql += " and (" + parser.parse(input.getExistsFilter()) + ")";
                }
                Rs exists = context.getDao().rs("select o.* from " + uiMeta.getMainTable() + " o where o." + existsSql);
                if (!exists.isNew()) {
                    parser.addVariable("value", v);
                    throw new JDiyFormException(StringUtils.isEmpty(input.getExistsMsg())
                            ? v + "在数据库中已经存在！"
                            : parser.parse(input.getExistsMsg()), input.getField());
                }
            }
            if (v != null) rs.set(cField, v);
        }

        if (__treeUiMeta == null && StringUtils.hasText(qo.get("_trPageId"))) {
            __treeUiMeta = dao.getUiMeta(qo.get("_trPageId"));
        }
        if (__treeUiMeta != null && rs.isNew()) {
            //添加时,先(随便)设置为顶层节点，等下面dao.save之后再真正更新树字段
            // (为了解决树表为自增主键时，保存之前id为空值，会导致相关的树字段出现null的bug)
            rs.set(__treeUiMeta.getPidField(), __treeUiMeta.getRootPid().getDataValue());
            rs.set(__treeUiMeta.getPathField(), ".");
        }
        boolean isNew = rs.isNew();
        handler.beforeSave(rs);
        context.getDao().save(rs);

        if (__treeUiMeta != null) {//对于自增主键，save之后才能拿到id, 因此必须save之后再来更新树字段
            String parentId = isNew && qo.get(__treeUiMeta.getPidField()) == null
                    ? qo.get("_trParentId")
                    : qo.get(__treeUiMeta.getPidField());

            if (isNew || qo.containsKey(__treeUiMeta.getPidField())) {//添加或者表单中有修改上级的控件，才需要更新树字段
                treeHelper.updateTreeFields(rs, __treeUiMeta, parentId, isNew);
            }
            context.getDao().save(rs);
        }


        //many类型的外表字段更新需要放在主表记录保存之后：
        for (InputMeta input : uiMeta.getInputs()) {
            String cField = StringUtils.isEmpty(input.getField()) ? input.getId() : input.getField();
            String v = qo.get(cField);
            if (InputTpl.lookup == input.getType() && input.getLookupType() != null) {
                switch (input.getLookupType()) {
                    case OneToMany:
                        __update_OneToMany_table(rs, dao.getUiMeta(input.getLookupId()).getMainTable(), input.getLookupJoinField(), v);
                        break;
                    case ManyToMany:
                        __update_ManyToMany(rs, input.getLookupJoinTable(), input.getLookupJoinField0(), input.getLookupJoinField1(), v);
                        break;
                }
            } else if (InputTpl.checkbox == input.getType() && input.getManyType() != null) {
                switch (input.getManyType()) {
                    case OneToMany:
                        if (input.getOptType() == OptTpl.table) {
                            __update_OneToMany_table(rs, input.getOptTable(), input.getOptValField(), v);
                        } else if (input.getOptType() == OptTpl.entity) {
                            if (!rs.isNew()) {
                                List<?> ls = em.createQuery("select o from " + input.getOptEntity() + " o where o."
                                                + input.getOptValField() + "=:v"
                                                + (StringUtils.hasText(v) ? " and o.id not in (" + v + ")" : ""))
                                        .setParameter("v", rs.id())
                                        .getResultList();
                                for (Object o : ls) {
                                    String[] ss = input.getOptValField().split("\\.");
                                    if (ss.length == 2 && "id".equalsIgnoreCase(ss[1])) { // subEntity.id=null -> subEntity=null
                                        BeanUtils.set(o, ss[0], null);
                                    } else {
                                        BeanUtils.set(o, input.getOptValField(), null);
                                    }
                                    em.merge(o);
                                }
                            }
                            if (StringUtils.hasText(v)) {
                                List<?> ls = em.createQuery("select o from " + input.getOptEntity() +
                                                " o where o.id in (" + v + ")")
                                        .getResultList();
                                for (Object o : ls) {
                                    BeanUtils.set(o, input.getOptValField(), rs.id());
                                    em.merge(o);
                                }
                            }
                        }
                        break;
                    case ManyToMany:
                        __update_ManyToMany(rs, input.getManyTable(), input.getManyField0(), input.getManyField1(), v);
                        break;
                }
            }
        }
        handler.afterSave(rs);
        return rs;
    }


    @Override
    @Transactional
    public void do_batchUpdate(Map<String, String> qo, FormUiMeta uiMeta, FormHandler handler, FtlParser parser) throws Exception {
        String[] _ids = qo.get("_ids").split(",");
        for (String id : _ids) {
            Rs rs = context.getDao().rs(uiMeta.getMainTable(), id.trim());
            if (rs.isNew()) continue;

            //隐藏域：
            if (uiMeta.getHiddenParams() != null && !"".equals(uiMeta.getHiddenParams())) {
                String[] sa = uiMeta.getHiddenParams().split("\\n");
                for (String ss : sa) {
                    String[] sb = ss.split(":");
                    if (sb.length == 2) {
                        String v = sb[1].trim(), f = sb[0].trim();
                        //if (rs.isNew() || f.startsWith("@")) { 批量更新表单，和普通表单不同，隐藏域是无条件更新
                        rs.set(f.startsWith("@") ? f.substring(1).trim() : f, parser.parse(v));
                        //}
                    }
                }
            }

            for (InputMeta input : uiMeta.getInputs()) {
                String cField = StringUtils.isEmpty(input.getField()) ? input.getId() : input.getField();
                String v = qo.get(cField);
                if (FormatTpl.pwdAgain == input.getFormat()) continue;

                switch (input.getType()) {
                    case input:
                        if (FormatTpl.pwd == input.getFormat()) {
                            if (v == null || "".equals(v)) continue;
                            if ("md5".equals(input.getPwdType())) {
                                v = Md5Utils.md5(v);
                            } else if ("md5salt".equals(input.getPwdType())) {
                                v = Md5Utils.encrypt(v);
                            }
                        } else if (FormatTpl.joinEntity == input.getFormat()) {
                            if (StringUtils.hasText(qo.get(cField))) {
                                List<?> ida = em.createQuery("select o.id from " + input.getJoinEntity() + " o where o." + input.getJoinField() + "='" +
                                                qo.get(cField).replaceAll("'", "''") + "'")
                                        .setMaxResults(1)
                                        .getResultList();
                                if (ida == null || ida.size() < 1 || ida.get(0) == null)
                                    throw new JDiyFormException("[" + input.getLabel() + "] 输入信息无效！", cField);
                                v = ida.get(0).toString();
                            } else {
                                v = null;
                            }
                            rs.set(cField, v);//外键返查v=null也要更新
                        } else if (FormatTpl.joinTable == input.getFormat()) {
                            if (StringUtils.hasText(qo.get(cField))) {
                                TableInfo joinTableInfo = context.getDao().getTableInfo(input.getJoinTable());
                                Rs oj = context.getDao().rs(new Args(input.getJoinTable(),
                                        input.getJoinField() + "='" + qo.get(cField) + "'", joinTableInfo.getPrimaryKey()));
                                if (oj.isNew())
                                    throw new JDiyFormException("[" + input.getLabel() + "] 输入信息无效！", cField);
                                v = oj.id();
                            } else {
                                v = null;
                            }
                            rs.set(cField, v);//外键返查v=null也要更新
                        }
                        break;
                    case switcher:
                        String[] va = input.getOnv().split("\\|");
                        v = va["on".equals(v) ? 0 : 1].trim();
                        break;
                    case checkbox:
                        if (ManyTpl.OneToMany == input.getManyType() || ManyTpl.ManyToMany == input.getManyType()) {
                            v = null;//many类型更新的是外部表，下面单独处理（且需放在本表save之后）
                        }
                        break;
                    case lookup:
                        if (ManyTpl.OneToMany == input.getLookupType() || ManyTpl.ManyToMany == input.getLookupType()) {
                            v = null;//many类型更新的是外部表，下面单独处理（且需放在本表save之后）
                        }
                        break;
                }
                //批量更新忽略验重
                if (v != null) rs.set(input.getField(), v);
            }
            handler.beforeSave(rs);
            context.getDao().save(rs);

            //many类型的外表字段更新需要放在主表记录保存之后：
            for (InputMeta input : uiMeta.getInputs()) {
                String cField = StringUtils.isEmpty(input.getField()) ? input.getId() : input.getField();
                String v = qo.get(cField);
                if (InputTpl.lookup == input.getType() && input.getLookupType() != null) {
                    switch (input.getLookupType()) {
                        case OneToMany:
                            throw new JDiyException("查找带回控件[" + input.getLabel() + "]为一对多外键反向更新模式，它与当前的批量更新表单逻辑冲突．请将其删除．");
                        case ManyToMany:
                            __update_ManyToMany(rs, input.getLookupJoinTable(), input.getLookupJoinField0(), input.getLookupJoinField1(), v);
                            break;
                    }
                } else if (InputTpl.checkbox == input.getType() && input.getManyType() != null) {
                    switch (input.getManyType()) {
                        case OneToMany:
                            throw new JDiyException("复选框控件[" + input.getLabel() + "]为一对多外键反向更新模式，它与当前的批量更新表单逻辑冲突．请将其删除．");
                        case ManyToMany:
                            __update_ManyToMany(rs, input.getManyTable(), input.getManyField0(), input.getManyField1(), v);
                            break;
                    }
                }
            }
            handler.afterSave(rs);
            handler.completeSave(rs);
        }
    }

    @Override
    @Transactional
    public void do_ajax(String uiType, String uid, String btnId, String[] id, FtlParser parser) {
        try {
            if ("list".equals(uiType)) {
                ListUiMeta uiMeta = dao.getUiMeta(uid);
                ButtonMeta buttonMeta = Arrays.stream(uiMeta.getRowButtons()).filter(o -> o.getId().equals(btnId)).findFirst().orElse(
                        Arrays.stream(uiMeta.getTopButtons()).filter(o1 -> o1.getId().equals(btnId)).findFirst().orElse(null)
                );
                if (buttonMeta == null) throw new JDiyException("获取目标按钮配置信息失败！");
                ListHandler handler = handlers.getHandler(uiMeta.getPageHandler(), ListHandler.class).orElse(new DefaultListHandler());

                Method m = this.getClass().getDeclaredMethod("do_" + buttonMeta.getAct().name(), String.class, String[].class, ButtonMeta.class, TlBaseHandler.class, FtlParser.class);
                m.invoke(this, uiMeta.getMainTable(), id, buttonMeta, handler, parser);
            } else if ("tree".equals(uiType)) {
                TreeUiMeta uiMeta = dao.getUiMeta(uid);
                ButtonMeta buttonMeta = Arrays.stream(uiMeta.getRowButtons()).filter(o -> o.getId().equals(btnId)).findFirst().orElse(
                        Arrays.stream(uiMeta.getTopButtons()).filter(o1 -> o1.getId().equals(btnId)).findFirst().orElse(null)
                );
                if (buttonMeta == null) throw new JDiyException("获取目标操作按钮配置信息失败！");
                TreeHandler handler = handlers.getHandler(uiMeta.getPageHandler(), TreeHandler.class).orElse(new DefaultTreeHandler());

                Method m = this.getClass().getDeclaredMethod("do_" + buttonMeta.getAct().name(), String.class, String[].class, ButtonMeta.class, TlBaseHandler.class, FtlParser.class);
                m.invoke(this, uiMeta.getMainTable(), id, buttonMeta, handler, parser);
            } else if ("view".equals(uiType)) {
                ViewUiMeta uiMeta = dao.getUiMeta(uid);
                ButtonMeta buttonMeta = Arrays.stream(uiMeta.getBtns()).filter(o -> o.getId().equals(btnId)).findFirst().orElse(null);
                if (buttonMeta == null) throw new JDiyException("获取目标按钮配置信息失败！");
                ViewHandler handler = handlers.getHandler(uiMeta.getPageHandler(), ViewHandler.class).orElse(new DefaultViewHandler());

                Method m = this.getClass().getDeclaredMethod("do_" + buttonMeta.getAct().name(), String.class, String[].class, ButtonMeta.class, TlBaseHandler.class, FtlParser.class);
                m.invoke(this, uiMeta.getMainTable(), id, buttonMeta, handler, parser);
            }
        } catch (InvocationTargetException e) {
            throw new JDiyException(e.getTargetException().getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            throw new JDiyException(e.getMessage());
        }
    }

    @SuppressWarnings("unused")
    private void do_del(String mainTable, String[] id, ButtonMeta btnit, TlBaseHandler handler, FtlParser parser) {
        try {
            Dao dao = appContext.getDao();
            String primaryKey = dao.create(mainTable).getPrimaryKey();
            Arrays.stream(id).filter(handler::onDelete).map(_id -> new Args(mainTable, primaryKey + "='" + _id + "'")).forEach(dao::del);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new JDiySqlException("删除失败，可能该条目已被业务系统使用。错误信息：" + ex.getMessage());
        }
    }

    @SuppressWarnings("unused")
    private void do_update(String mainTable, String[] id, ButtonMeta btnit, TlBaseHandler handler, FtlParser parser) {
        try {
            Dao dao = appContext.getDao();
            for (String _id : id) {
                if (handler.onUpdate(_id)) {
                    Rs rs = dao.rs(mainTable, _id);
                    if (!rs.isNew()) {
                        parser.addVariable("vo", rs);
                        dao.exec("update `" + mainTable + "` set " +
                                parser.parse(btnit.getUpdateCode().replaceAll("\\n", ","))
                                + " where " + rs.getPrimaryKey() + "='" + _id + "'");
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new JDiySqlException("执行此操作失败，返回错误信息：" + ex.getMessage());
        }
    }

    @SuppressWarnings("unused")
    private void do_exec(String mainTable, String[] ids, ButtonMeta btnit, TlBaseHandler handler, FtlParser parser) {
        Arrays.stream(ids).forEach(s -> handler.execute(s, btnit));
    }

    @Override
    public <T extends UiMeta> T getUiMeta(String id) {
        return dao.getUiMeta(id);
    }

    private void __update_OneToMany_table(Rs rs, String table, String joinField, String value) {
        TableInfo tableInfo = context.getDao().getTableInfo(table);
        if (!rs.isNew())
            context.getDao().exec("update " + table + " set "
                    + joinField + "=null where "
                    + joinField + "='" + rs.id() + "'"
                    + (StringUtils.hasText(value) ? " and " + tableInfo.getPrimaryKey() + " not in (" + value + ")" : ""));

        if (StringUtils.hasText(value))
            context.getDao().exec("update " + table + " set " + joinField + "='" + rs.id() +
                    "' where " + tableInfo.getPrimaryKey() + " in(" + value + ")");
    }

    public void __update_ManyToMany(Rs rs, String joinTable, String joinColumn, String revJoinColumn, String value) {
        if (!rs.isNew())
            context.getDao().exec("delete from " + joinTable + " where " + joinColumn + "='" + rs.id() + "'");
        if (StringUtils.hasText(value))
            Arrays.stream(value.split(",")).forEach(s ->
                    context.getDao().exec("insert into " + joinTable
                            + " (" + joinColumn + "," + revJoinColumn + ") values ('"
                            + rs.id() + "'," + s + ")")
            );
    }


    @Resource
    private AppContext appContext;
    @Resource
    GuidDao guidDao;
    @Resource
    private TreeHelper treeHelper;
    @Resource
    private Handlers handlers;
    @PersistenceContext
    private EntityManager em;
}
